// CommunityMegaphoneReader.cpp : Defines the entry point for the application.
//

#include <windows.h>
#include <aygshell.h>
#include <htmlctrl.h>
#include <connmgr.h>
#include <msxml.h>
#include <objsafe.h>

#include "CommunityMegaphoneReader.h"
#include "GPS.h"

#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
#define PI 3.14159

typedef struct __eventdetails
{
	BSTR bstrLink;
	BSTR bstrTitle;
	BSTR bstrDescription;
	BSTR bstrLatitude;
	BSTR bstrLongitude;

	double dblDistance;
} EventDetails;

// Calculate the distance between two locations in miles
static double CalculateDistanceBetweenPoints_Miles(double lat1, double lng1, double lat2, double lng2)
{ 
	double radius = 3956.0; // miles (use 6367.0 if we want kilometers)

	// Convert from degrees into radians
	lat1 = lat1 * (PI / 180);
	lng1 = lng1 * (PI / 180);
	lat2 = lat2 * (PI / 180);
	lng2 = lng2 * (PI / 180);
	
	// Finally calculate the great circle distance
	return radius * 2 * asin(min(1, sqrt((pow(sin((lat2 - lat1) / 2.0), 2.0) + cos(lat1) * cos(lat2) * pow(sin((lng2 - lng1) / 2.0), 2.0)))));
}

// Checks if a valid network connection is present that
// will enable us to access the url 'pszUrl'. If one is
// not present it will attempt to establish one.
static HANDLE CreateNetworkConnection(LPCTSTR pszUrl)
{
	CONNMGR_CONNECTIONINFO ci = {0};
	HANDLE hConnection = NULL;
	DWORD dwStatus = 0;
	HRESULT hr; 

	// Register with the connection manager
	ci.cbSize = sizeof(CONNMGR_CONNECTIONINFO); 
	ci.dwParams = CONNMGR_PARAM_GUIDDESTNET; 
	ci.dwFlags = CONNMGR_FLAG_PROXY_HTTP; 
	ci.dwPriority = CONNMGR_PRIORITY_USERINTERACTIVE; 

	// Ask connection manager which network would best
	// enable us to connect to the URL we aim to use.
	hr = ConnMgrMapURL(pszUrl, &ci.guidDestNet, &dwStatus);
	if (hr != S_OK)
		return NULL;

	// Then request connection manager to establish a
	// connection to that network.
	hr = ConnMgrEstablishConnectionSync(&ci, &hConnection, 25000, &dwStatus); 
	if (hr != S_OK)
		return NULL;

	return hConnection;
}

static void CloseNetworkConnection(HANDLE hConnection)
{
   if (hConnection)
   {
	   // Tell connection manager we don't need access to the
	   // network anymore. Notice that this may not immediatly
	   // disconnect the network connection, as connection manager
	   // may have other clients that concurrently desire access,
	   // or it may decide to "cache" the connection in case
	   // another application wants to access the same netowrk in
	   // the near future.
       ConnMgrReleaseConnection(hConnection, TRUE);
   }
}

// Executes an XPATH query against an XML document node to select
// a single node and then returns the associated text value.
static void GetString(IXMLDOMNode *pDoc, LPCTSTR pszXPath, BSTR *pbstrValue)
{
	IXMLDOMNode *pNode = NULL;
	BSTR bstrXPath = SysAllocString(pszXPath);

	*pbstrValue = NULL;

	// Execute the XPATH query
	if (pDoc->selectSingleNode(bstrXPath, &pNode) == S_OK
		&& pNode != NULL)
	{
		// If the expression resulted in a node being
		// selected obtain it's text value.
		pNode->get_text(pbstrValue);

		pNode->Release();
		pNode = NULL;
	}

	SysFreeString(bstrXPath);
}

// Populate the EventDetails structure pointed to by pEventDetails with the
// contents extracted from the XML subtree pointed to by pXMLNode
static void ParseEventDetails(EventDetails *pEventDetails, IXMLDOMNode * pXMLNode, double dblCurrentLat, double dblCurrentLon)
{
	// Get the text properties
	GetString(pXMLNode, _T("link"), &pEventDetails->bstrLink);
	GetString(pXMLNode, _T("title"), &pEventDetails->bstrTitle);
	GetString(pXMLNode, _T("description"), &pEventDetails->bstrDescription);

	// Extract the latitude and longitude and convert
	// into a distance from the user's current position
	GetString(pXMLNode, _T("geo:lat"), &pEventDetails->bstrLatitude);
	GetString(pXMLNode, _T("geo:long"), &pEventDetails->bstrLongitude);

	if (dblCurrentLat == -1 || dblCurrentLon == -1
		|| !pEventDetails->bstrLatitude
		|| !pEventDetails->bstrLongitude)
	{
		// If we don't have the user's current position, or
		// the event doesn't have an associated position, assume
		// the distance is 0.
		pEventDetails->dblDistance = 0.0;
	}
	else
	{
		// We have the user's current position and the event
		// has an associated position so calculate the distance
		// between the two points.
		double dblTargetLat = _tcstod(pEventDetails->bstrLatitude, NULL);
		double dblTargetLon = _tcstod(pEventDetails->bstrLongitude, NULL);

		pEventDetails->dblDistance = CalculateDistanceBetweenPoints_Miles(dblCurrentLat, dblCurrentLon, dblTargetLat, dblTargetLon); 
	}
}

// Generates an HTML snippet for the details of a single event
static void GenerateEventHTML(EventDetails *pEventDetails, LPTSTR pszHTML)
{
	_stprintf(pszHTML, 
		_T("<span style=\"color: #FFFFFF;font-weight:bold;\">") \
		_T("<a href=\"%s\" style=\"color: #DBB94F;\">%s</a><br/><br/>") \
		_T("%s<br/><br/>") \
		_T("<b>Distance: <span style=\"color: #DBB94F;\">%0.2f miles</span></b><br/>") \
		_T("<br/><br/></span>"),
		pEventDetails->bstrLink,
		pEventDetails->bstrTitle,
		pEventDetails->bstrDescription,
		pEventDetails->dblDistance);
}

static int CompareEventsByDistance(const void *a, const void *b)
{
	return (int)(((EventDetails*)a)->dblDistance - ((EventDetails*)b)->dblDistance);
}

static void CallWebService(HWND hDlg)
{
	LPCTSTR pszUrl = _T("http://www.communitymegaphone.com/madEventRss.ashx");

	// Determine the current GPS position, setting
	// latitude and longitude to -1 if no position
	// information is available.
	GPS_POSITION gpsPosition;
	GPS_GetData(&gpsPosition, NULL);

	if (!(gpsPosition.dwValidFields & GPS_VALID_LATITUDE)
		|| !(gpsPosition.dwValidFields & GPS_VALID_LONGITUDE))
	{
		gpsPosition.dblLatitude = -1;
		gpsPosition.dblLongitude = -1;
	}

	// Ensure we have a suitable connection to the network
	HANDLE hConnection = CreateNetworkConnection(pszUrl);
	if (!hConnection)
	{
		MessageBox(GetForegroundWindow(), _T("Failed to connect to the internet"), _T("Error"), MB_ICONERROR | MB_OK);
		return;
	}

	// Create an XML Document instance
	IXMLDOMDocument2 *pDoc = NULL;
	HRESULT hr = CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument2, (void**)&pDoc);
	if (hr == S_OK)
	{
		// Fetch the event list from the Community Megaphone
		// service. MSXML is able to fetch data from HTTP URLs
		// directly.
		VARIANT_BOOL bSuccess = VARIANT_FALSE;
		VARIANT filename;

		VariantInit(&filename);
		V_BSTR(&filename) = SysAllocString(pszUrl);
		V_VT(&filename) = VT_BSTR;

		pDoc->put_async(VARIANT_FALSE);

		hr = pDoc->load(filename, &bSuccess);
		if (hr == S_OK && bSuccess == VARIANT_TRUE)
		{
			static TCHAR szHTML[32768];
			IXMLDOMNodeList *pNodeList = NULL;

			// Find all the events listed within the RSS feed
			BSTR bstr = SysAllocString(_T("/rss/channel/item"));
			hr = pDoc->selectNodes(bstr, &pNodeList);
			if (hr == S_OK && pNodeList != NULL)
			{
				IXMLDOMNode * pXMLNode;

				LONG lEventCount = 0;
				hr = pNodeList->get_length(&lEventCount);
				if (hr == S_OK)
				{
					// Allocate an array to contain all the event details
					EventDetails *pEvents = (EventDetails *)malloc(sizeof(EventDetails) * lEventCount);
					memset(pEvents, 0, sizeof(EventDetails) * lEventCount);

					// Extract all the event details out of the
					// XML DOM and into our array
					for (LONG i = 0; i < lEventCount; i++)
					{
						hr = pNodeList->get_item(i, &pXMLNode);
						if (hr == S_OK && pXMLNode)
						{
							ParseEventDetails(&pEvents[i], pXMLNode, gpsPosition.dblLatitude, gpsPosition.dblLongitude);
							pXMLNode->Release();
						}
   					}
	
					// Sort our array of event details by
					// ascending distance values
					qsort(pEvents, lEventCount, sizeof(*pEvents), CompareEventsByDistance);
	
					// Iterate over the array adding each
					// event (converted into an HTML snippet)
					// to the web browser control
					SendDlgItemMessage(hDlg, IDC_HTMLVIEW, DTM_CLEAR, 0, 0);
					SendDlgItemMessage(hDlg, IDC_HTMLVIEW, DTM_ADDTEXTW, 0, (LPARAM)_T("<html><body bgcolor=#656565>"));
					for (LONG i = 0; i < lEventCount; i++)
					{
						GenerateEventHTML(&pEvents[i], szHTML);
						SendDlgItemMessage(hDlg, IDC_HTMLVIEW, DTM_ADDTEXTW, 0, (LPARAM)szHTML);
					}
					SendDlgItemMessage(hDlg, IDC_HTMLVIEW, DTM_ADDTEXTW, 0, (LPARAM)_T("</body></html>"));
					SendDlgItemMessage(hDlg, IDC_HTMLVIEW, DTM_ENDOFSOURCE, 0, 0);

					// Tidy up the memory we have allocated
					for (LONG i = 0; i < lEventCount; i++)
					{
						SysFreeString(pEvents[i].bstrTitle);
						SysFreeString(pEvents[i].bstrLink);
						SysFreeString(pEvents[i].bstrDescription);
						SysFreeString(pEvents[i].bstrLatitude);				
						SysFreeString(pEvents[i].bstrLongitude);
					}
					free(pEvents);
				}

				pNodeList->Release();
			}
		}

		pDoc->Release();
		pDoc = NULL;
	}

	CloseNetworkConnection(hConnection);
}

// Message handler for Community Megaphone Reader Dialog.
INT_PTR CALLBACK CommunityMegaphoneReaderDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_INITDIALOG:
            {
                // Create a Done button and size it.  
                SHINITDLGINFO shidi;
                shidi.dwMask = SHIDIM_FLAGS;
                shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIPDOWN | SHIDIF_SIZEDLGFULLSCREEN | SHIDIF_EMPTYMENU;
                shidi.hDlg = hDlg;
                SHInitDialog(&shidi);

				// Create a menu bar
				SHMENUBARINFO mbi;
                memset(&mbi, 0, sizeof(SHMENUBARINFO));
				mbi.cbSize     = sizeof(SHMENUBARINFO);
				mbi.hwndParent = hDlg;
				mbi.nToolBarId = IDR_MENU;
				mbi.hInstRes   = GetModuleHandle(NULL);
				mbi.dwFlags = SHCMBF_HMENU | SHCMBF_HIDESIPBUTTON;
	            SHCreateMenuBar(&mbi);

				// Pretend the user selected the Start GPS menu item
				// so that the application starts off with the GPS
				// enabled
				PostMessage(hDlg, WM_COMMAND, IDM_GPS_START, 0);
            }
            return (INT_PTR)TRUE;

		case WM_INITMENUPOPUP:
			// The menu is about to popup so enable/disable
			// the various menu items depending upon the state
			// of the application.
			if (GPS_IsOpened())
			{
				EnableMenuItem((HMENU)wParam, IDM_GPS_START, MF_BYCOMMAND | MF_GRAYED);
				EnableMenuItem((HMENU)wParam, IDM_GPS_STOP,  MF_BYCOMMAND | MF_ENABLED);
			}
			else
			{
				EnableMenuItem((HMENU)wParam, IDM_GPS_START, MF_BYCOMMAND | MF_ENABLED);
				EnableMenuItem((HMENU)wParam, IDM_GPS_STOP,  MF_BYCOMMAND | MF_GRAYED);
			}
			break;

        case WM_COMMAND:
            switch (LOWORD(wParam))
			{
				case IDOK:
		            EndDialog(hDlg, LOWORD(wParam));
					return (INT_PTR)TRUE;

				case IDM_GPS_START:
					if (!GPS_IsOpened())
					{
						GPS_Open();
					}
					break;
           
				case IDM_GPS_STOP:
					if (GPS_IsOpened())
					{
						GPS_Close();
					}
					break;

				case IDM_UPDATE:
					{
						HCURSOR hCursor = SetCursor(LoadCursor(GetModuleHandle(NULL), IDC_WAIT));
						CallWebService(hDlg);
						SetCursor(hCursor);
					}
					break;
            }
            break;

		case WM_SIZE:
			{
				// Resize the HTML control to cover the entire
				// client area
				HWND hwndHTML = GetDlgItem(hDlg, IDC_HTMLVIEW);
				SetWindowPos(hwndHTML, NULL, 0, 0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER);
			}
			break;

        case WM_CLOSE:
            EndDialog(hDlg, message);
            return (INT_PTR)TRUE;

    }
    return (INT_PTR)FALSE;
}

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPTSTR    lpCmdLine,
                   int       nCmdShow)
{
	CoInitializeEx(NULL, COINIT_MULTITHREADED);
	InitHTMLControl(hInstance);
	
	return DialogBox(hInstance, (LPCTSTR)IDD_COMMUNITYMEGAPHONEREADER, NULL, CommunityMegaphoneReaderDialog);
}
